package cn.jtfadmin.base.lang.common.service;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import cn.afterturn.easypoi.handler.impl.ExcelDataHandlerDefaultImpl;
import cn.jtfadmin.base.lang.common.domain.dto.*;
import cn.jtfadmin.base.lang.common.domain.entity.IdEntity;
import cn.jtfadmin.base.lang.common.utils.AkUtils;
import cn.jtfadmin.base.lang.common.utils.JsonUtils;
import cn.jtfadmin.base.lang.common.utils.ReflectionUtils;
import cn.jtfadmin.base.lang.config.ApplicationHolder;
import cn.jtfadmin.base.lang.config.excel.ExcelParam;
import cn.jtfadmin.base.lang.config.excel.ExcelResultHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * abstract baseService
 * @author jtf
 * @since 0.0.1
 * @param <T>
 */
public abstract class AbstractBaseService<T  extends IdEntity> implements BaseService<T>{


    /****************** 按照id查询 实体 *****************************/
    /**
     * 按照id查询
     *
     * @param id id
     * @return 数据
     */
    public abstract Optional<T> findById(@NotNull String id);

    /**
     * 按照id查询
     *
     * @param ids id,id,...
     * @return 数据
     */
    public List<T> findByIds(@NotBlank String ids){
        if(!StringUtils.hasText(ids)){
            return Collections.emptyList();
        }
        Set<String> idsSet = StringUtils.commaDelimitedListToSet(ids);
        idsSet.remove("");
        return findByIds(idsSet);
    }

    /**
     * 按照id查询
     *
     * @param ids ids
     * @return 数据
     */
    public abstract List<T> findByIds(@NotNull Collection<String> ids);



    /**
     * 按照id查询实体
     *
     * @param id
     * @return 实体
     * @throws NoSuchElementException 当数据没有找到时抛出
     */
    public T findRequiredById(@NotNull String id) throws NoSuchElementException {
        Optional<T> optional = findById(id);
        if(optional.isPresent()){
            return optional.get();
        }
        throw new NoSuchElementException("查询的数据不存在:id:" + id);
    }


    /****************** 按照id查询 模型 *****************************/

    /**
     * 按照id查询
     *
     * @param id id
     * @param modelClass modelClass
     * @return 数据
     */
    public <V> Optional<V> findModelById(@NotNull String id, @NotNull Class<V> modelClass){
        Optional<T> optional = findById(id);
        if(optional.isPresent()){
            return Optional.of(AkUtils.copyProperties(optional.get(), modelClass));
        }else{
            return Optional.empty();
        }
    }

    /**
     * 按照id查询
     *
     * @param ids id,id,...
     * @param modelClass modelClass
     * @return 数据
     */
    public <V> List<V> findModelByIds(@NotBlank String ids,  @NotNull Class<V> modelClass){
        return findByIds(ids).stream().map(t->AkUtils.copyProperties(t, modelClass)).collect(Collectors.toList());
    }

    /**
     * 按照id查询
     *
     * @param ids ids
     * @param modelClass modelClass
     * @return 数据
     */
    public <V> List<V>  findModelByIds(@NotNull List<String> ids, @NotNull Class<V> modelClass){
        return findByIds(ids).stream().map(t->AkUtils.copyProperties(t, modelClass)).collect(Collectors.toList());
    }
    /**
     * 按照id查询实体
     *
     * @param id
     * @param modelClass modelClass
     * @return 实体
     * @throws NoSuchElementException 当数据没有找到时抛出
     */
    public  <V>  V findModelRequiredById(@NotNull String id, @NotNull Class<V> modelClass) throws NoSuchElementException {
        return AkUtils.copyProperties(findRequiredById(id), modelClass);
    }




    /****************** 按照字段查询 模型 *****************************/

    /**
     * 按照字段获取一个
     * @param fieldName 字段名
     * @param value 字段值
     * @return
     */
    public Optional<T> findOneByField(@NotBlank String fieldName, Object value){
        List<T> list = findByField(fieldName, value);
        if(CollectionUtils.isEmpty(list)){
            return Optional.empty();
        }
        if(list.size()>1){
            throw new IllegalArgumentException("所查询的数据有"+list.size()+"条：字段类："+getEntityClass().getName()+", 字段名:"+fieldName+"，字段值："+value);
        }
        return Optional.of(list.iterator().next());
    }


    /**
     * 按照字段查询-
     * @param fieldName 字段名
     * @param value 字段值
     * @return
     */
    public T findRequiredOneByField(@NotBlank String fieldName, Object value){
        Optional<T> optional = findOneByField(fieldName, value);
        if(optional.isPresent()){
            return optional.get();
        }
        throw new NoSuchElementException("所查询的数据不存在：字段类："+getEntityClass().getName()+", 字段名:"+fieldName+"，字段值："+value);
    }

    /****************** 按照查询条件查询  *****************************/
    /**
     * 按照字段查询
     * @param fieldName 字段名
     * @param value 字段值
     * @return
     */
    public List<T> findByField(@NotBlank String fieldName, Object value){
        SpecConditionDTO specConditionDTO;
        if(value == null){
            specConditionDTO = new SpecConditionDTO("isNull", String.valueOf(value), fieldName);
        }else {
            specConditionDTO = new SpecConditionDTO("eq", String.valueOf(value), fieldName);
        }

        return findAll(Arrays.asList(specConditionDTO));
    }


    /**
     * 查询所有
     *
     * @param specPDTO 查询条件和排序条件
     * @return
     */
    public List<T> findAll(@Nullable SpecPDTO<T> specPDTO){
        if(Objects.isNull(specPDTO)){
            return findAll(null,null, null);
        }
        String specJson = specPDTO.getSpecJson();
        List<SpecConditionDTO> specConditions = null;
        if(StringUtils.hasText(specJson)){
            try {
                specConditions = JsonUtils.constuctListType(URLDecoder.decode(specJson,"utf-8"), SpecConditionDTO.class);
            } catch (UnsupportedEncodingException e) {
                // ignore
            }
        }
        return findAll(specConditions,
                specPDTO.getColumn(),
                specPDTO.getOrder()
        );
    }


    /**
     * 查询参数
     * @param specConditions
     * @return
     */
    public List<T> findAll(@Nullable List<SpecConditionDTO> specConditions){
        return findAll(specConditions, null, null);
    }


    /**
     * 查询所有
     * @param specConditions 查询条件
     * @param orderColumn 排序列
     * @param order 排序 desc/asc
     * @return
     */
    public abstract List<T> findAll(@Nullable List<SpecConditionDTO> specConditions, @Nullable String orderColumn, @Nullable String order);


    /**
     * 查询所有
     * @param specConditions 查询条件
     * @param orderColumn 排序列
     * @param order 排序 desc/asc
     * @param pageNo pageNo
     * @param pageSize pageSize
     * @return
     */
    public abstract PageRDTO<T> findAll(@Nullable List<SpecConditionDTO> specConditions, @Nullable String orderColumn, @Nullable String order, int pageNo, int pageSize);

    /**
     * 分页查询
     *
     * @param specPagePDTO 条件和分页
     * @return
     */
    public PageRDTO<T> findAll(@NotNull SpecPagePDTO<T> specPagePDTO){
        String specJson = specPagePDTO.getSpecJson();
        List<SpecConditionDTO> specConditions = null;
        if(StringUtils.hasText(specJson)){
            try {
                specConditions = JsonUtils.constuctListType(URLDecoder.decode(specJson,"utf-8"), SpecConditionDTO.class);
            } catch (UnsupportedEncodingException e) {
                // ignore
            }
        }
        return findAll(specConditions,
                    specPagePDTO.getColumn(),
                    specPagePDTO.getOrder(),
                    specPagePDTO.getPageNo(),
                    specPagePDTO.getPageSize()
                );
    }

    public abstract long count(@Nullable List<SpecConditionDTO> specConditions);


    /****************** 按照查询条件查询  模型 *****************************/

    @Override
    public <V> List<V> findModelAll(SpecPDTO<T> specPDTO, @NotNull Class<V> modelClass) {
        return findAll(specPDTO).stream().map(t->AkUtils.copyProperties(t, modelClass)).collect(Collectors.toList());
    }

    @Override
    public <V> PageRDTO<V> findModelAll(SpecPagePDTO<T> specPDTO, @NotNull Class<V> modelClass) {
        PageRDTO<T> p = findAll(specPDTO);
        List<V> content =p.getContent().stream().map(t -> AkUtils.copyProperties(t, modelClass)).collect(Collectors.toList());
        PageRDTO<V> result = new PageRDTO<>();
        result.setTotalElements(p.getTotalElements());
        result.setContent(content);
        return result;
    }


    /****************** 按照查询条件查询  删除 *****************************/
    /**
     * 按照id删除
     *
     * @param id id
     */
    public void deleteById(@NotBlank String id){
        if(StringUtils.hasText(id)){
            deleteBatch(Arrays.asList(id));
        }
    }

    /**
     * 按照id 批量删除
     *
     * @param ids id,id,...
     */
    public void deleteBatch(@NotBlank String ids){
        if(StringUtils.hasText(ids)){
            Set<String> idsSet = StringUtils.commaDelimitedListToSet(ids);
            idsSet.remove("");
            if(!CollectionUtils.isEmpty(idsSet)){
                deleteBatch(idsSet);
            }

        }
    }

    /**
     * 批量删除
     *
     * @param ids
     */
    public abstract void deleteBatch(@NotNull Collection<String> ids) ;

    /****************** 新增 *****************************/
    /**
     * 新增
     * @param addDTO 参数
     * @return 结果
     */
    @Override
    public T add(@NotNull @Validated Object addDTO){
        Class<?> addClass = getAddClass();
        if(Objects.equals(addDTO.getClass(), addClass)){
            return doAdd(addDTO);
        }
        throw new IllegalArgumentException("新增参数类型错误");
    }
    /**
     * 获取新增class
     * @return 新增类型
     */
    protected abstract  Class<?> getAddClass();

    /**
     * 实际新增
     * @param addPDTO 参数
     * @return 结果
     */
    protected   T doAdd(Object addPDTO){
        return save(AkUtils.copyProperties(addPDTO, getEntityClass()));
    }

    /****************** 编辑 *****************************/

    /**
     * 新增
     * @param editDTO 参数
     * @return 结果
     */
    @Override
    public T edit(@NotNull @Validated EditPDTO editDTO){
        Class<?> editClass = getEditClass();
        if(Objects.equals(editDTO.getClass(), editClass)){
            return doEdit(editDTO);
        }
        throw new IllegalArgumentException("编辑参数类型错误");
    }

    /**
     * 获取编辑class
     * @return 编辑类型
     */
    protected abstract Class<? extends EditPDTO> getEditClass();

    /**
     * 实际新增
     * @param editPDTO 编辑参数
     * @param <V> 编辑参数类型
     * @return 编辑后结果
     */
    protected  <V extends EditPDTO> T doEdit(V editPDTO){
        String id = editPDTO.getId();
        T entity = findRequiredById(id);
        AkUtils.copyProperties(editPDTO, entity);
        return save(entity);
    }

    /*********************** 导入导出 ******************************/
    /**
     * 导出excel
     * @param specPDTO
     * @param outputStream
     */
    public void exportExcel(SpecPDTO<T> specPDTO, OutputStream outputStream){
        List<T> list = findAll(specPDTO);
        List<?> all = entityToExcelDTO(list);
        ExportParams exportParams = ExcelParam.getExportParams();
        exportParams = fillExcelExportParams(exportParams);
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, getExcelClass(), all);
        try {
            workbook.write(outputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                workbook.close();
            } catch (IOException e) {
                //ignore
            }
        }

    }
    protected List<?> entityToExcelDTO(List<T> entities){
        return entities.stream().map(t->AkUtils.copyProperties(t,getExcelClass())).collect(Collectors.toList());
    }

    protected ExportParams fillExcelExportParams(ExportParams exportParams){
        return exportParams;
    }

    protected abstract Class<? extends BaseExcelModalDTO> getExcelClass();

    /**
     * 导入excel
     * @param stream
     */
    public void importExcel(InputStream stream){
        //导入参数
        ImportParams importParams = ExcelParam.getImportParams();
        // 解决 时间 导入格式错误
        Class<? extends BaseExcelModalDTO> excelClass = getExcelClass();
        Set<Class<?>> transterClass = new HashSet<>();
        transterClass.add(LocalTime.class);
        transterClass.add(LocalDate.class);
        transterClass.add(LocalDateTime.class);
        Set<String> collect = ReflectionUtils.getDeclaredFields(excelClass).stream().filter(t -> transterClass.contains(t.getType()) && t.getAnnotation(Excel.class) != null)
                .map(t -> t.getAnnotation(Excel.class).name()).collect(Collectors.toSet());
        ConversionService bean = ApplicationHolder.getApplicationContext().getBean(ConversionService.class);
        ExcelDataHandlerDefaultImpl excelDataHandler = new ExcelDataHandlerDefaultImpl(){
            @Override
            public Object importHandler(Object obj, String name, Object value) {
                if(value == null){
                    return null;
                }
                Optional<Field> optional = ReflectionUtils.getDeclaredFields(obj.getClass()).stream()
                        .filter(t -> t.getAnnotation(Excel.class) != null && t.getAnnotation(Excel.class).name().equals(name))
                        .findFirst();
                if(optional.isPresent()){
                    return bean.convert(value, optional.get().getType());
                }
                return super.importHandler(obj, name, value);
            }
        };
        excelDataHandler.setNeedHandlerFields(collect.toArray(new String[0]));
        importParams.setDataHandler(excelDataHandler);

        // 自定义导入参数
        importParams = fillExcelImportParams(importParams);
        List<BaseExcelModalDTO> list = new ArrayList<>();
        try {
            // 导入结果
            ExcelImportResult<? extends BaseExcelModalDTO> result = ExcelImportUtil.importExcelMore(stream, getExcelClass(), importParams);
            if (result.isVerifyFail()) {
                List<? extends BaseExcelModalDTO> failList = result.getFailList();
                throw new IllegalArgumentException(
                        failList.stream().map(t -> ("第"+((BaseExcelModalDTO) t).getRowNum()) + "行:" + t.getErrorMsg()).collect(Collectors.joining(System.lineSeparator()))
                );
            }
            String[] excelResultHandlerBeanNames = ApplicationHolder.getApplicationContext().getBeanNamesForType(ExcelResultHandler.class);

            for (BaseExcelModalDTO excelModalDTO : result.getList()) {
                for (String excelResultHandlerBeanName : excelResultHandlerBeanNames) {
                    excelModalDTO = ApplicationHolder.getApplicationContext().getBean(excelResultHandlerBeanName, ExcelResultHandler.class).handle(excelModalDTO);
                }
                list.add(excelModalDTO);
            }
        } catch (Exception e) {
            if(e instanceof RuntimeException){
                throw (RuntimeException)e;
            }else {
                throw new RuntimeException(e);
            }

        }
        List<T> entities = excelDTOToEntity(list);
        for (T entity : entities) {
            beforeSave(entity);
        }
        doSaveAll(entities);
        for (T entity : entities) {
            afterSave(entity);
        }
    }

    /**
     * 表格数据转entity, 简单的复制属性
     * @param list 表格数据
     * @param <V>
     * @return
     */
    protected <V extends BaseExcelModalDTO> List<T> excelDTOToEntity(List<V> list){
        return list.stream().map(t->AkUtils.copyProperties(t,getEntityClass())).collect(Collectors.toList());
    }

    /**
     * 自定义导入数据
     * @param importParams
     */
    protected ImportParams fillExcelImportParams(ImportParams importParams){
        return importParams;
    }


   public T save(@NotNull T t){
       beforeSave(t);
       T result = doSave(t);
       afterSave(result);
       return result;
   }



    /**
     * 保存之前处理
     * @param t
     */
    protected void beforeSave(T t){

    }

    /**
     * 保存之后处理
     * @param t
     */
    protected void afterSave(T t){

    }

    /**
     * 保存
     * @param t 实体
     * @return 保存
     */
    protected abstract T doSave(@NotNull T t);

    /**
     * 保存全部
     * @param iterable
     */
    protected abstract void doSaveAll(@NotNull Iterable<T> iterable);

    /**
     * 获取解析实体类
     * @return
     */

    public Class<T> getEntityClass() {
        ResolvableType resolvableType = ResolvableType.forClass(getClass());
        ResolvableType type1 = resolvableType.as(BaseService.class);
        return (Class<T>) type1.getGeneric(0).resolve();
    }





}
